﻿using DotNetty.Codecs.Http;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Handlers.Tls;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using System.Collections.Concurrent;
using System.Net;

namespace DotNetty.Extensions
{
    public class WebSocketServer : IAsyncDisposable
    {
        public WebSocketServer(int port, IPAddress ipAddress = null, string path = "/")
        {
            _port = port;
            _ipAddress = ipAddress;
            _ipAddress ??= IPAddress.Any;
            Path = path;
        }

        private readonly int _port;

        private readonly IPAddress _ipAddress;

        public string Path { get; set; }

        public bool UseSsl { get; internal set; }

        public int MaxFramePayloadLength { get; set; } = 5 * 1024 * 1024;

        public readonly ConcurrentDictionary<string, WebSocketConnection> ClientDict = [];

        public int SoBacklog { get; set; } = 8192;

        public bool TcpNodelay { get; set; } = true;

        public int BossGroupCount { get; set; } = 0;

        public int WorkerGroupCount { get; set; } = 0;

        private IEventLoopGroup bossGroup;

        private IEventLoopGroup workerGroup;

        private IChannel channel;

        private readonly SemaphoreSlim _slim = new(1, 1);

        private bool _slimDispose = false;

        public event Action<ServerBootstrap> OnCreateBootstrap;

        public event Action<IChannelPipeline> OnChannelPipeline;

        public event Action OnStarting;

        public event Action OnStarted;

        public event Action<Exception> OnStopped;

        public event Action<WebSocketConnection> OnClientConnected;

        public event Action<WebSocketConnection, string> OnClientTextMessage;

        public event Action<WebSocketConnection, byte[]> OnClientBinaryMessage;

        public event Action<WebSocketConnection, Exception> OnClientException;

        public event Action<WebSocketConnection> OnClientDisconnected;

        internal void ClientConnected(IChannelHandlerContext context)
        {
            var cnn = new WebSocketConnection(context.Channel);
            ClientDict.TryAdd(context.Channel.Id.AsShortText(), cnn);
            try
            {
                OnClientConnected?.Invoke(cnn);
            }
            catch { }
        }

        internal void ClientTextMessage(IChannelHandlerContext context, string msg)
        {
            var ok = ClientDict.TryGetValue(context.Channel.Id.AsShortText(), out var cnn);
            if (!ok)
            {
                return;
            }

            try
            {
                OnClientTextMessage?.Invoke(cnn!, msg);
            }
            catch { }
        }

        internal void ClientBinaryMessage(IChannelHandlerContext context, byte[] bytes)
        {
            var ok = ClientDict.TryGetValue(context.Channel.Id.AsShortText(), out var cnn);
            if (!ok)
            {
                return;
            }

            try
            {
                OnClientBinaryMessage?.Invoke(cnn!, bytes);
            }
            catch { }
        }

        internal void ClientException(IChannelHandlerContext context, Exception ex)
        {
            var ok = ClientDict.TryGetValue(context.Channel.Id.AsShortText(), out var cnn);
            if (!ok)
            {
                return;
            }

            try
            {
                OnClientException?.Invoke(cnn!, ex);
            }
            catch { }
        }

        internal void ClientDisconnected(IChannelHandlerContext context)
        {
            var ok = ClientDict.TryRemove(context.Channel.Id.AsShortText(), out var cnn);
            if (!ok)
            {
                return;
            }

            try
            {
                OnClientDisconnected?.Invoke(cnn!);
            }
            catch { }
        }

        private async Task CloseGroupAsync()
        {
            if (bossGroup == null)
            {
                return;
            }

            try
            {
                var task1 = bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
                var tasl2 = workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
                await Task.WhenAll(task1, tasl2);
            }
            catch { }
        }

        private async Task StartBaseAsync()
        {
            try
            {
                if (BossGroupCount <= 0)
                {
                    bossGroup = new MultithreadEventLoopGroup();
                }
                else
                {
                    bossGroup = new MultithreadEventLoopGroup(BossGroupCount);
                }

                if (WorkerGroupCount <= 0)
                {

                    workerGroup = new MultithreadEventLoopGroup();
                }
                else
                {
                    workerGroup = new MultithreadEventLoopGroup(WorkerGroupCount);
                }

                var bootstrap = new ServerBootstrap()
                    .Group(bossGroup, workerGroup)
                    .Option(ChannelOption.SoBacklog, SoBacklog)
                    .Channel<TcpServerSocketChannel>()
                    .ChildOption(ChannelOption.TcpNodelay, TcpNodelay)
                    .ChildHandler(new ActionChannelInitializer<IChannel>(ch =>
                    {
                        IChannelPipeline pipeline = ch.Pipeline;
                        OnChannelPipeline?.Invoke(pipeline);

                        var tls = pipeline.FirstOrDefault(f => f.GetType() == typeof(TlsHandler));
                        UseSsl = tls != null;

                        pipeline.AddLast(new HttpServerCodec());
                        pipeline.AddLast(new HttpObjectAggregator(65536));
                        pipeline.AddLast(new WebSocketServerProtocolHandler(Path, null, true, MaxFramePayloadLength, false, true));
                        pipeline.AddLast(new WebSocketServerHandler(this));

                    }));

                OnCreateBootstrap?.Invoke(bootstrap);

                try
                {
                    OnStarting?.Invoke();
                }
                catch { }

                channel = await bootstrap.BindAsync(_ipAddress, _port);

                try
                {
                    OnStarted?.Invoke();
                }
                catch { }

            }
            catch (Exception ex)
            {
                await CloseGroupAsync();

                try
                {
                    OnStopped?.Invoke(ex);
                }
                catch { }
            }
        }

        public async Task StartAsync()
        {
            if (_slimDispose)
            {
                return;
            }

            try
            {
                var ok = await _slim.WaitAsync(0);

                if (!ok) //等不到信号量，表示被停止了，直接返回
                {
                    return;
                }

                await StartBaseAsync();

                _slim.Release();
            }
            catch { }
        }

        public async ValueTask DisposeAsync()
        {
            await _slim.WaitAsync();
            _slimDispose = true;

            if (channel != null)
            {
                try
                {
                    //关闭服务器通道
                    await channel.CloseAsync();
                    channel = null;
                }
                catch { }
            }

            await CloseGroupAsync();

            try
            {
                OnStopped?.Invoke(null);
            }
            catch { }

            _slim.Release();
            _slim.Dispose();

            GC.SuppressFinalize(this);
        }
    }
}
